package cn.jsu.oj.dispatchServer.satellite.heart;

import cn.jsu.oj.dispatchServer.satellite.annotation.StarterAnn;
import cn.jsu.oj.dispatchServer.satellite.config.ResourceConfig;
import cn.jsu.oj.dispatchServer.satellite.netty.starter.Starter;
import cn.jsu.oj.dispatchServer.satellite.pojo.net.PackNet;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 心跳服务
 *
 * @author shan
 * @date 2022/06/29
 */
@Slf4j
@Component("heartClientService")
@StarterAnn(starter = 3)
public class HeartClientService implements Starter {

    @Autowired
    Scheduler scheduler;

    private Trigger heartTrigger;

    private Trigger timeoutTrigger;

    private JobKey heartJob;

    private JobKey timeoutJob;

    private Boolean isResponse;

    private final LinkedBlockingQueue<Object> responseQueue = new LinkedBlockingQueue<>(1024);

    private static final Integer HEART_SLEEP = ResourceConfig.getConfigInteger("client-heart-sleep");

    private static final Integer HEART_TIMEOUT = ResourceConfig.getConfigInteger("client-heart-timeout");

    private static final String CRON_EXPRESS = ResourceConfig.getConfigString("client-heart-response");

    public Boolean getResponse() {
        return isResponse;
    }

    public void setResponse(Boolean response) {
        isResponse = response;
    }

    /**
     * 响应压入
     *
     * @param response 响应
     */
    public void addResponse(Object response) {
//        log.info("----------响应压入......");
        try {
            this.responseQueue.put(response);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void brokenHeart() {
        if (this.heartTrigger == null || this.timeoutTrigger == null) {
            return;
        }
        TriggerKey heartKey = this.heartTrigger.getKey();
        TriggerKey timeoutKey = this.timeoutTrigger.getKey();
        removeSchedule(heartKey, this.heartJob);
        removeSchedule(timeoutKey, this.timeoutJob);
        this.heartTrigger = null;
        this.timeoutTrigger = null;
    }

    private void removeSchedule(TriggerKey triggerKey, JobKey jobKey) {
        try {
            scheduler.pauseTrigger(triggerKey);
            scheduler.unscheduleJob(triggerKey);
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建心跳
     *
     * @param identity 身份
     */
    private void buildHeart(String identity) {
        this.heartTrigger = buildHeartTrigger(identity);
        JobDetail jobDetail = JobBuilder
                .newJob(HeartClientHeartJob.class)
                .withIdentity(identity, "heartJob")
                .build();
        this.heartJob = jobDetail.getKey();
        try {
            scheduler.scheduleJob(jobDetail, heartTrigger);
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.error("----------心跳构建失败......");
            log.error(Arrays.toString(e.getStackTrace()));
        }
    }

    /**
     * 构建心跳触发器
     *
     * @param triggerId 触发器id
     * @return {@link CronTrigger }
     */
    private CronTrigger buildHeartTrigger(String triggerId) {
        return TriggerBuilder.newTrigger()
                .withIdentity(triggerId, "heartGroup")
                .startAt(new Date(System.currentTimeMillis() + HeartClientService.HEART_SLEEP))
                .withSchedule(CronScheduleBuilder
                        .cronSchedule(HeartClientService.CRON_EXPRESS)
                        .withMisfireHandlingInstructionDoNothing())
                .build();
    }

    /**
     * 建立超时
     *
     * @param identity 身份
     */
    private void buildTimeout(String identity) {
        Trigger trigger = buildTimeoutTrigger(identity);
        this.timeoutTrigger = trigger;
        JobDetail jobDetail = JobBuilder
                .newJob(HeartClientTimeoutJob.class)
                .withIdentity(identity, "timeoutJob")
                .build();
        this.timeoutJob = jobDetail.getKey();
        try {
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建超时触发器
     *
     * @param triggerId 触发器id
     * @return {@link Trigger }
     */
    private Trigger buildTimeoutTrigger(String triggerId) {
        return TriggerBuilder.newTrigger()
                .withIdentity(triggerId, "timeoutGroup")
                .startNow()
                .withSchedule(SimpleScheduleBuilder
                        .simpleSchedule()
                        .withIntervalInSeconds(HEART_TIMEOUT)
                        .withRepeatCount(1))
                .build();
    }

    /**
     * 刷新触发器
     *
     * @param oldTrigger 旧触发器
     * @param identity   身份
     */
    private void refreshTrigger(TriggerKey oldTrigger, String identity) {
        log.info(identity + ": 触发器刷新......");
        Trigger newTrigger = buildTimeoutTrigger(identity);
        Date date = null;
        try {
            date = scheduler.rescheduleJob(oldTrigger, newTrigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        if (date == null) {
            log.error(identity + ": 刷新失败......");
        }
    }

    /**
     * 心跳响应读取线程
     */
    @Override
    @SuppressWarnings("InfiniteLoopStatement")
    public void start() {
        log.info("----------等待心跳到达......");
        while (true) {
            String identity = null;
            try {
                identity = ((PackNet<?>) this.responseQueue.take()).getIdentity();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.error("----------响应弹出失败......");
                log.error(Arrays.toString(e.getStackTrace()));
            }
            this.isResponse = true;
            log.info("----------心跳控制中心响应到达......");
            if (this.heartTrigger == null) {
                buildHeart(identity);
                buildTimeout(identity);
            } else {
                refreshTrigger(timeoutTrigger.getKey(), identity);
            }
        }
    }
}
